<?php
namespace Cola;
use Cola\Ext\Cache\Redis;
use Cola\Ext\Pdo;
use Cola\Ext\Validator;

/**
 *
 * @property \Cola\Config $config
 * @property \Cola\Ext\Cache\Redis $redis
 * @property \Cola\Ext\Pdo $db
 * @property \Cola\Ext\Cache\Base $cache
 *
 */
abstract class Model
{
    protected $_invalidErrorCode = -400;

    /**
     * Db name
     *
     * @var string
     */
    protected $_db = '_db';

    /**
     * Table name, with prefix and main name
     *
     * @var string
     */
    protected $_table;

    /**
     * Primary key
     *
     * @var string
     */
    protected $_pk = 'id';

    /**
     * Cache config
     *
     * @var mixed, string for config key and array for config
     */
    protected $_cache = '_cache';

    /**
     * Cache expire time
     *
     * @var int
     */
    protected $_ttl = 86400;

    /**
     * Validator rules
     *
     * @var array
     */
    protected $_rules = array();

    /**
     * 存储查询语句中的一些关键项
     *
     * @var array
     */
    protected $_option = [];

    /**
     * Error infomation
     *
     * @var array
     */
    public $error = array();

    /**
     * 是否为debug模式
     *
     * @var bool
     */
    public $isDebug = false;

    public function __construct()
    {
    	$this->resetOption();
    	method_exists($this, '_init') && $this->_init();
    }

    /**
     * 设置查询语句中要选择的列
     *
     * @param string $field 要选择的列
     * @return object $this
     */
    public function filed($field = '*')
    {
    	$this->_option['field'] = $field;
    	return $this;
    }

    /**
     * 设置查询语句中的排序项
     *
     * @param string $order 排序项
     * @return object $this
     */
    public function order($order = '')
    {
    	$this->_option['order'] = $order;
    	return $this;
    }

    /**
     * Get a result row
     *
     * @param string|int $where
     * @param string $table
     * @return array
     */
    public function row($where, $table = null)
    {
    	$table = $table ? : $this->_table;
		$field = $this->_option['field'];
    	if(is_numeric($where))
    	{
    		$where = "$this->_pk='$where'";
    	}
    	$sql = "select $field from $table"
    			. ($where ? " where $where" : '')
    			. ($this->_option['order'] !='' ? " order by $this->_option[order]" : '');
    	$row = $this->db->row($sql);
    	$this->resetOption();
    	return $row;
    }

    /**
     * Count result
     *
     * @param string $where
     * @param string $table
     * @return int
     */
    public function count($where, $table = null)
    {
    	return $this->db->count($where, $table ? : $this->_table);
    }

    /**
     * Get SQL result
     *
     * @param string $sql
     * @param array $data
     * @return array
     */
    public function sql($sql, $data = array())
    {
    	return $this->db->sql($sql, $data);
    }

    /**
     * Insert
     *
     * @param array $data
     * @param string $table
     * @return boolean
     */
    public function insert($data, $table = null)
    {
    	return $this->db->insert($data, $table ? : $this->_table);
    }

    /**
     * Replace
     *
     * @param array $data
     * @param string $table
     * @return boolean
     */
    public function replace($data, $table = null)
    {
    	return $this->db->replace($data, $table ? : $this->_table);
    }

    /**
     * Update
     *
     * @param array $data
     * @param int $id
     * @param string $table
     * @return boolean
     */
    public function update($data, $id, $table = null)
    {
    	return $this->db->update($data, ["{$this->_pk}=?", [$id]], $table ? : $this->_table);
    }

    public function mupdate($todos)
    {
        foreach ($todos as $id => $data) {
            if (!$this->update($data, $id)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Delete
     *
     * @param      $id
     * @param null $col
     *
     * @return boolean
     */
    public function delete($id, $col = null)
    {
        is_null($col) && $col = $this->_pk;
        $sql = "delete from {$this->_table} where {$col} = ?";

        try {
            return $this->db->sql($sql, array($id));
        } catch (\Exception $e) {
            $this->error = array('code' => $e->getCode(), 'msg' => $e->getMessage());
            return false;
        }
    }

    public function del($id, $col = null)
    {
        return $this->delete($id, $col);
    }

    public function mdel($ids, $col = null)
    {

    }

    /**
     * Connect db from config
     *
     * @param string
     *
     * @return \Cola\Ext\Pdo
     *
     * @throws \Cola\Exception\DbException
     */
    public function db($name = null)
    {
        is_null($name) && $name = $this->_db;

        if (is_array($name)) {
        	$name += array('host'=>$_ENV['mysql']['host'], 'port'=>$_ENV['mysql']['port'], 'user' => $_ENV['mysql']['user'], 'password' => $_ENV['mysql']['pwd'], 'options' => array());
            return new Pdo(
            	"mysql:dbname=$name[db];host=$name[host];port=$name[port]", $name['user'], $name['password'], $name['options']
            );
        }

        $regName = "_cola_db_{$name}";
        if (!$db = Cola::getReg($regName)) {
            $config = (array)Cola::getConfig($name)
            + array('host'=>$_ENV['mysql']['host'], 'port'=>$_ENV['mysql']['port'], 'user' => $_ENV['mysql']['user'], 'password' => $_ENV['mysql']['pwd'], 'options' => array());
            $db = new Pdo(
            	"mysql:dbname=$config[db];host=$config[host];port=$config[port]", $config['user'], $config['password'], $config['options']
            );
            Cola::setReg($regName, $db);
        }

        return $db;
    }

    /**
     * Init Cola_Ext_Cache
     *
     * @param mixed $name
     * @return \Cola\Ext\Cache\Base
     */
    public function cache($name = null)
    {
        is_null($name) && ($name = $this->_cache);

        if (is_array($name)) {
            return Cola::factory('Cola_Ext_Cache', $name);
        }

        $regName = "_cola_cache_{$name}";
        if (!$cache = Cola::getReg($regName)) {
            $config = (array)Cola::getConfig($name);
            $cache = Cola::factory('Cola_Ext_Cache', $config);
            Cola::setReg($regName, $cache);
        }

        return $cache;
    }

    /**
     * Init Cola_Ext_Redis
     * @param string $name
     * @return \Cola\Ext\Cache\Redis
     */
    public function redis($name = null)
    {
        $redis = $this->cache($name ? : '_redis_front');
        if($redis instanceof Redis)
        {
            return $redis;
        }

        return null;
    }

    /**
     * Get function cache
     *
     * @param string $func
     * @param mixed $args
     * @param int $ttl
     * @param string $key
     * @return mixed
     */
    public function cached($func, $args = array(), $ttl = null, $key = null)
    {
        is_null($ttl) && ($ttl = $this->_ttl);

        if (!is_array($args)) {
            $args = array($args);
        }

        if (is_null($key)) {
            $key = get_class($this) . '-' . $func . '-' . sha1(serialize($args));
        }

        if (!$data = $this->cache->get($key)) {
            $data = json_encode(call_user_func_array(array($this, $func), $args));
            $this->cache->set($key, $data, $ttl);
        }

        return json_decode($data, true);
    }

    /**
     * Validate
     *
     * @param array $data
     * @param boolean $ignoreNotExists
     * @param array $rules
     * @return boolean
     */
    public function validate($data, $ignoreNotExists = false, $rules = null)
    {
        is_null($rules) && $rules = $this->_rules;
        if (empty($rules)) {
            return true;
        }

        $validator = new Validator();

        $result = $validator->check($data, $rules, $ignoreNotExists);

        if (!$result) {
            $this->error = array(
                'code' => $this->_invalidErrorCode,
                'msg'  => current($validator->errors),
                'ref' => $validator->errors
            );
            return false;
        }

        return true;
    }

    /**
     * Dynamic set vars
     *
     * @param string $key
     * @param mixed $value
     */
    public function __set($key, $value = null)
    {
        $this->$key = $value;
    }

    /**
     * Dynamic get vars
     *
     * @param string $key
     *
     * @return Config|Ext\Cache\Base|Redis|Pdo
     * @throws \Cola\Exception
     * @throws Exception\DbException
     */
    public function __get($key)
    {
        switch ($key) {
            case 'db' :
                $this->db = $this->db();
                return $this->db;

            case 'cache' :
                $this->cache = $this->cache();
                return $this->cache;

            case 'redis' :
                $this->redis = $this->redis();
                return $this->redis;

            case 'config':
                $this->config = Cola::getInstance()->config();
                return $this->config;

            default:
                throw new Exception('Undefined property: ' . get_class($this). '::' . $key);
        }
    }

    /**
     * 记入日志文件
     * @param string $loginfo 日志内容
     * @param bool $echo 是否输出日志信息
     * @param bool $exit 签名检测为错误时是否中止程序
     */
    public function logfile($loginfo, $echo = false, $exit = false)
    {
        // 只取类名，但是这样做的话，相同类名不同命名空间的log就会放在一起 by suika
        if (empty($this->_logfile))
        {
            $tmp = explode('\\', static::class);
            if(!empty($tmp))
            {
                $this->_logfile = $tmp[count($tmp) - 1];
            }
        }
    	logfile($this->_logfile, $loginfo, $echo, $exit);
    }

    /**
     * 重置option属性
     */
    public function resetOption()
    {
    	$this->_option = ['field'=>'*', 'order'=>''];
    }

    /**
     * 实例化模型
     * @param string $name
     * @return \Cola\Model
     */
    protected function model($name = null)
    {
        /* @var \Cola\Model $model */
        $model = Cola::model($name);
        $model->isDebug = $this->isDebug;
        return $model;
    }
}
